package com.tools.web;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.tools.common.exception.InvalidOperationException;
import com.tools.common.object.Note;
import com.tools.db.jdbc.DBSource;
import com.tools.db.jdbc.LocalDateTimeTypeHandler;
import com.tools.web.filter.FilterSource;
import javax.servlet.Filter;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.util.unit.DataSize;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import java.time.LocalDateTime;
import java.util.Map;

/**
 * Spring 常用 Bean 的声明，容器的操作
 * 等等工具类
 * */
@Note("Spring 常用 Bean 的声明，容器的操作等等工具类")
public final class SpringBeanKit {

    private SpringBeanKit() {}

    @Note("Spring 容器接口实例")
    private static ApplicationContext CONTEXT;

    /* ************************************************************************************************************
     *
     *
     *          Spring 容器的操作方法
     *
     * ************************************************************************************************************
     * */

    @Note("初始化 Spring 容器属性，只会设置一次值")
    public static void initApplicationContext(ApplicationContext context) {
        if(CONTEXT != null) return;
        synchronized (SpringBeanKit.class) {
            if(CONTEXT != null) return;
            if(context == null) throw new NullPointerException("不能设置 null 的 ApplicationContext");
            CONTEXT = context;
        }
    }

    @Note("判断本工具类包装的 Spring 容器属性是否已被初始化")
    public static boolean beanContextIsInit() {
        return CONTEXT != null;
    }


    @Note("根据 Bean 对象的类型获取 Bean 对象")
    public static <T> T getBean(Class<T> beanType) {
        if(CONTEXT == null) throw new InvalidOperationException("ApplicationContext 尚未在该类中初始化，" +
                "请在启动类的 main 方法中调用 static void setApplicationContext() 接收容器初始化");
        return CONTEXT.getBean(beanType);
    }


    @Note("根据 ID 获取 Bean 对象")
    public static Object getBean(String id) {
        if(CONTEXT == null) throw new InvalidOperationException("ApplicationContext 尚未在该类中初始化，" +
                "请在启动类的 main 方法中调用 static void setApplicationContext() 接收容器初始化");
        return CONTEXT.getBean(id);
    }

    @Note("根据 ID 获取 Bean 对象，并将其强制转为指定类型")
    public static <T> T getBean(String id, Class<T> beanType) {
        if(CONTEXT == null) throw new InvalidOperationException("ApplicationContext 尚未在该类中初始化，" +
                "请在启动类的 main 方法中调用 static void setApplicationContext() 接收容器初始化");
        return CONTEXT.getBean(id, beanType);
    }


    @Note("获取 Spring 容器实例的引用")
    public static ApplicationContext getApplicationContext() {
        return CONTEXT;
    }

    /* ************************************************************************************************************
     *
     *
     *          常用 Bean 的定义
     *
     * ************************************************************************************************************
     * */



    @Note("在实现了 WebMvcConfigurer 接口的 addCorsMappings(CorsRegistry registry)" +
            "中调用，即可完成跨域配置")
    public static void corsDefaultConfig(CorsRegistry registry, int maxAge) {
        if(maxAge <= 0) maxAge = 1800;
        registry.addMapping("/**")  //所有接口
                .allowedHeaders("*")        //允许所有请求头
                .allowedMethods("*")        //允许任何请求方式
                .maxAge(maxAge)             //在多长时间内，不需要发出第二次预检请求。单位为秒。
                .allowedOriginPatterns("*")           // 支持请求的域
                .allowCredentials(true);     //是否发送 Cookie
                //.exposedHeaders("*")        //开放所有头

    }



    @Note("默认的跨域的过滤器")
    public static CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource ubc = new UrlBasedCorsConfigurationSource();
        CorsConfiguration cc = new CorsConfiguration();
        cc.addAllowedHeader("*");
        cc.addAllowedMethod("*");
        cc.addAllowedOriginPattern("*");
        cc.setAllowCredentials(true);
        cc.setMaxAge(1800L);
        ubc.registerCorsConfiguration("/**", cc);
        return new CorsFilter(ubc);
    }


    @Note("在 Spring 容器中注册一个 Filter（需要加上 @Bean 注解）")
    public static FilterRegistrationBean<Filter> registrationFilterBean(FilterSource filterSource) {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setDispatcherTypes(filterSource.getDispatcherTypes());
        registration.setFilter(filterSource.getFilter());
        registration.addUrlPatterns(filterSource.getUrlPatterns());
        registration.setName(filterSource.getName());
        registration.setOrder(filterSource.getOrder());
        registration.setEnabled(filterSource.isEnable());
        Map<String, String> initParameter = filterSource.getInitParameter();
        if(initParameter != null && !initParameter.isEmpty()) {
            for (Map.Entry<String, String> e : initParameter.entrySet()) {
                registration.addInitParameter(e.getKey(), e.getValue());
            }
        }
        return registration;
    }


    @Note("在 Spring 容器中注册一个 HttpServletRequest 请求的监听器（需要加上 @Bean 注解）")
    public static ServletListenerRegistrationBean<ServletRequestListener> registrationListenerBean(int order, ServletRequestListener listener) {
        if(listener == null) throw new NullPointerException("HttpServletRequest 请求监听器不能为 null");
        if(order < 0) throw new IllegalArgumentException("HttpServletRequest 请求监听器执行顺序不能为负数");
        ServletListenerRegistrationBean<ServletRequestListener> registration = new ServletListenerRegistrationBean<>();
        registration.setOrder(order);
        registration.setEnabled(true);
        registration.setListener(listener);
        return registration;
    }


    @Note("mybatis-plus 的自定义的分页插件对象。" +
            "这是较高版本的写法，旧版本的直接 【return new PaginationInterceptor()】 即可")
    public static MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }


    @Note("返回文件上传对象的配置对象，比如【上传文件的极限大小】 和 【最大请求大小】")
    public static MultipartConfigElement multipartConfigElement(String maxFileSize, String maxRequestSize) {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize(DataSize.parse(maxFileSize));
        factory.setMaxRequestSize(DataSize.parse(maxRequestSize));
        return factory.createMultipartConfig();
    }


    @Note("WebSocket 的关键 Bean 实例。"+
            "这个 Bean 会自动注册使用了 @ServerEndpoint 注解声明的 Websocket endpoint" +
            "需要将其配置到 @Configuration 注解的 Bean 的 @Bean 里")
    public static ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }


    @Note("注册 MyBatis 处理 LocalDateTime 类型的 TypeHandler 类型处理器到 Spring 容器内")
    public static ConfigurationCustomizer mybatisLocalDateTimeTypeHandlerConfigurationCustomizer() {
        return configuration -> {
            configuration.getTypeHandlerRegistry().register(LocalDateTime.class, new LocalDateTimeTypeHandler());
        };
    }


    @Note("手动注册个 DruidDataSource 连接池对象，用于兼容高版本的 SpringBoot")
    public static DruidDataSource druidDataSource(DBSource source) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(source.getDriverClass());
        dataSource.setUrl(source.getUrl());
        dataSource.setUsername(source.getUsername());
        dataSource.setPassword(source.getPassword());
        dataSource.setInitialSize(source.getInitialSize());
        dataSource.setMinIdle(source.getMinIdle());
        dataSource.setMaxActive(source.getMaxActive());
        dataSource.setMaxWait(source.getMaxWait());
        return dataSource;
    }

    @Note("调试打印项目中的所有接口，可以在 ApplicationRunner 的 run 方法中调用")
    public static void printfAllURIPath(RequestMappingHandlerMapping mapping) {
        mapping.getHandlerMethods().forEach((requestMappingInfo, handlerMethod) -> {
            System.out.println(requestMappingInfo + " -> " + handlerMethod);
        });
    }
}
